package com.beardedhen.harmonybootstrap;

import com.beardedhen.harmonybootstrap.api.attributes.BootstrapBrand;
import com.beardedhen.harmonybootstrap.api.defaults.DefaultBootstrapBrand;
import com.beardedhen.harmonybootstrap.api.defaults.DefaultBootstrapSize;
import com.beardedhen.harmonybootstrap.api.view.BootstrapBrandView;
import com.beardedhen.harmonybootstrap.api.view.BootstrapSizeView;
import com.beardedhen.harmonybootstrap.api.view.BorderView;
import com.beardedhen.harmonybootstrap.utils.ResUtil;
import com.beardedhen.harmonybootstrap.utils.UiUtil;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.AlphaType;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

/**
 * BootstrapCircleThumbnails display a circular image with an optional border, that can be themed
 * using BootstrapBrand colors. The view extends ImageView, and will automatically center crop and
 * scale images.
 */

public class BootstrapCircleThumbnail extends Image implements Component.DrawTask, BootstrapBrandView, BorderView, BootstrapSizeView {

    private static final float DEFAULT_VALUE_TWO = 2;

    private static final String BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_BRAND = "bootstrapBrand";

    private static final String BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_SIZE = "bootstrapSize";

    private static final String BOOTSTRAP_CIRCLE_THUMBNAIL_HAS_BORDER = "hasBorder";

    protected boolean hasBorder;
    protected BootstrapBrand bootstrapBrand;
    protected final Paint borderPaint = new Paint();
    protected Paint imagePaint = new Paint();
    protected final Paint placeholderPaint = new Paint();
    protected PixelMap sourceBitmap;
    private Canvas mBitmapCanvas;
    private final RectF pixelmapRect = new RectF();
    private boolean mIsInitialized;
    private static final ScaleMode SCALE_TYPE = ScaleMode.CENTER;
    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();
    private float mBorderRadius;
    private static final int DEFAULT_BORDER_WIDTH = 4;
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private static final float DEFAULT_VALUE_TWO_FLOAT = 2.0f;
    private static final float DEFAULT_VALUE_FOR_SMALLER_IMAGE = 15.0f;
    private static final float DEFAULT_VALUE_ONE_FLOAT = 1.0f;
    private static final float DEFAULT_VALUE_POINT_FIVE = 0.5f;
    private float mDrawableRadius;
    private Matrix mShaderMatrix = new Matrix();
    private boolean mIsRebuildShader;
    private static final float DEFAULT_IMAGE_ALPHA = 255;
    private float mImageAlpha = DEFAULT_IMAGE_ALPHA;
    private static final PixelFormat BITMAP_CONFIG = PixelFormat.ARGB_8888;


    public BootstrapCircleThumbnail(Context context) {
        super(context);
        initialise(null);
    }

    public BootstrapCircleThumbnail(Context context, AttrSet attrs) {
        super(context, attrs);
        initialise(attrs);
    }

    public BootstrapCircleThumbnail(Context context, AttrSet attrs, String defStyle) {
        super(context, attrs, defStyle);
        initialise(attrs);
    }


    protected void initialise(AttrSet attrs) {
        if(attrs == null) {
            return;
        }
        addDrawTask(this::onDraw);
        int typeOrdinal = -1;
        if (attrs.getAttr(BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_BRAND).isPresent()) {
            String typeOrdinalStr = attrs.getAttr(BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_BRAND).get().getStringValue().trim();
            typeOrdinal = UiUtil.getBootstrapBrandInt(typeOrdinalStr);
        }

        int sizeOrdinal = -1;
        if (attrs.getAttr(BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_SIZE).isPresent()) {
            String sizeOrdinalStr = attrs.getAttr(BOOTSTRAP_CIRCLE_THUMBNAIL_BOOTSTRAP_SIZE).get().getStringValue().trim();
            sizeOrdinal = UiUtil.getBootstrapSizeInt(sizeOrdinalStr);
        }

        hasBorder = !attrs.getAttr(BOOTSTRAP_CIRCLE_THUMBNAIL_HAS_BORDER).isPresent() || attrs.getAttr(
            BOOTSTRAP_CIRCLE_THUMBNAIL_HAS_BORDER).get().getBoolValue();


        if (typeOrdinal == -1) { // override to use Primary for default border (looks nicer)
            bootstrapBrand = DefaultBootstrapBrand.PRIMARY;
        } else {
            bootstrapBrand = DefaultBootstrapBrand.fromAttributeValue(typeOrdinal);
        }

        setupPaints();
        init();
    }

    private void init() {
        mIsInitialized = true;
        super.setScaleMode(SCALE_TYPE);
        imagePaint.setDither(true);
        imagePaint.setFilterBitmap(true);
        imagePaint.setAlpha(mImageAlpha);
        setLayoutRefreshedListener(new RefreshListener());
        addDrawTask(this);
    }
    private void setupPaints() {
        int placeholderColor = ResUtil.getColor(getContext(), ResourceTable.Color_bootstrap_gray_light);

        borderPaint.setAntiAlias(true);
        borderPaint.setStrokeWidth(mBorderWidth);
        borderPaint.setStyle(Paint.Style.STROKE_STYLE);
        imagePaint.setAntiAlias(true);

        placeholderPaint.setColor(new Color(placeholderColor));
        placeholderPaint.setAntiAlias(true);
        placeholderPaint.setStyle(Paint.Style.FILL_STYLE);
    }

    @Override
    public void setScaleMode(ScaleMode scaleType) {
        if (scaleType != ScaleMode.CLIP_CENTER) {
            throw new IllegalArgumentException("Only CenterCrop is currently supported by this view");
        } else {
            super.setScaleMode(scaleType);
        }
    }

    @Override
    public ScaleMode getScaleMode() {
        return ScaleMode.CLIP_CENTER;
    }
    /*
     * Image setter overrides
     */
    @Override
    public void setPixelMap(PixelMap pixelMap) {
        PixelMap emptyPixelMap = createEmptyPixelMap(pixelMap);
        super.setPixelMap(emptyPixelMap);
        sourceBitmap = pixelMap;
        initializeBitmap();
        invalidate();
    }

    @Override
    public void setImageElement(Element element) {
        PixelMap pixelMap = getBitmapForView(element);
        PixelMap emptyPixelMap = createEmptyPixelMap(pixelMap);
        super.setPixelMap(emptyPixelMap);
        sourceBitmap = pixelMap;
        initializeBitmap();
        invalidate();
    }

    @Override
    public void setPixelMap(int resId) {
        super.setPixelMap(resId);
        initializeBitmap();
        invalidate();
    }

    private PixelMap getBitmapForView(Element element) {
        Element drawable = element;

        if (drawable == null) {
            return null;
        }

        if (drawable instanceof PixelMapElement) {
            return ((PixelMapElement) drawable).getPixelMap();
        } else {
            int w = drawable.getWidth();
            int h = drawable.getHeight();

            PixelMap pixelMap;
            PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
            initializationOptions.size = new Size(w, h);
            initializationOptions.pixelFormat = BITMAP_CONFIG;
            pixelMap = PixelMap.create(initializationOptions);
            drawable.drawToCanvas(new Canvas(new Texture(pixelMap)));
            return pixelMap;
        }
    }

    public PixelMap createEmptyPixelMap(PixelMap pixelMap) {
        if (pixelMap == null) {
            return null;
        }

        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.size = new Size(pixelMap.getImageInfo().size.width, pixelMap.getImageInfo().size.height);
        initializationOptions.alphaType = AlphaType.UNKNOWN;
        return PixelMap.create(initializationOptions);
    }

    private void initializeBitmap() {
        if (sourceBitmap != null && sourceBitmap.isEditable()) {
            mBitmapCanvas = new Canvas();
            float radius = (sourceBitmap.getImageInfo().size.width) / DEFAULT_VALUE_TWO;
            mBitmapCanvas.drawPixelMapHolderCircleShape(new PixelMapHolder(sourceBitmap), pixelmapRect, 0, 0,
                    radius);
        } else {
            mBitmapCanvas = null;
        }

        if (!mIsInitialized) {
            return;
        }

        if (sourceBitmap != null) {
            updateShaderMatrix(); /* if commented, img does not come */
        } else {
            imagePaint.setShader(null, Paint.ShaderType.PIXELMAP_SHADER);
        }
    }

    @Override
    public void setBootstrapBrand(BootstrapBrand bootstrapBrand)  {
        this.bootstrapBrand = bootstrapBrand;
        invalidate();
    }

    @Override
    public BootstrapBrand getBootstrapBrand() {
        return bootstrapBrand;
    }

    @Override
    public void setBorderDisplayed(boolean displayed)  {
        hasBorder = displayed;
        invalidate();
    }

    @Override
    public boolean isBorderDisplayed() {
         return hasBorder;
    }

    @Override
    public float getBootstrapSize() {
        return 0;
    }

    @Override
    public void setBootstrapSize(float bootstrapSize) {
    }
    @Override
    public void setBootstrapSize(DefaultBootstrapSize bootstrapSize) {
    }
    class RefreshListener implements LayoutRefreshedListener {
        @Override
        public void onRefreshed(Component component) {
            updateDimensions();
            invalidate();
        }
    }
    private void  updateDimensions() {

        mBorderRect.set(calculateBounds());
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / DEFAULT_VALUE_TWO_FLOAT,
                (mBorderRect.width() - mBorderWidth) / DEFAULT_VALUE_TWO_FLOAT);

        mDrawableRect.set(mBorderRect);
        if (mBorderWidth > 0) {

            mDrawableRect.inset(mBorderWidth - DEFAULT_VALUE_ONE_FLOAT, mBorderWidth - DEFAULT_VALUE_ONE_FLOAT);
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / DEFAULT_VALUE_TWO_FLOAT,
                mDrawableRect.width() / DEFAULT_VALUE_TWO_FLOAT);
        mDrawableRadius= (float) (mDrawableRadius-DEFAULT_VALUE_FOR_SMALLER_IMAGE);
        updateShaderMatrix();
    }
    private void updateShaderMatrix() {
        if (sourceBitmap == null) {
            return;
        }
        float scale;
        float dx = 0;
        float dy = 0;

        mShaderMatrix.setIdentity();
        int bitmapHeight = sourceBitmap.getImageInfo().size.height;
        int bitmapWidth = sourceBitmap.getImageInfo().size.width;
        if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) {
            scale = mDrawableRect.height() / (float) bitmapHeight;
            dx = (mDrawableRect.width() - bitmapWidth * scale) * DEFAULT_VALUE_POINT_FIVE;
        } else {
            scale = mDrawableRect.width() / (float) bitmapWidth;
            dy = (mDrawableRect.height() - bitmapHeight * scale) * DEFAULT_VALUE_POINT_FIVE;
        }
        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + DEFAULT_VALUE_POINT_FIVE) + mDrawableRect.left,
                (int) (dy + DEFAULT_VALUE_POINT_FIVE) + mDrawableRect.top);
        mIsRebuildShader = true;
    }

    private RectF calculateBounds() {
        int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
        int sideLength = Math.min(availableWidth, availableHeight);
        float left = getPaddingLeft() + (availableWidth - sideLength) / DEFAULT_VALUE_TWO_FLOAT;
        float top = getPaddingTop() + (availableHeight - sideLength) / DEFAULT_VALUE_TWO_FLOAT;
        return new RectF(left, top, left + sideLength, top + sideLength);
    }

    @Override
    public void onDraw(Component view, Canvas canvas) {
        if (sourceBitmap != null) {
            if (mIsRebuildShader) {
                mIsRebuildShader = false;
                PixelMapHolder pixelmapholder = new PixelMapHolder(sourceBitmap);
                Shader pixelMapShader = new PixelMapShader(pixelmapholder, Shader.TileMode.CLAMP_TILEMODE,
                        Shader.TileMode.CLAMP_TILEMODE);
                pixelMapShader.setShaderMatrix(mShaderMatrix);
                imagePaint.setShader(pixelMapShader, Paint.ShaderType.PIXELMAP_SHADER);
            }

            if(hasBorder){
                canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, imagePaint);}
            else{
                canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mBorderRadius, imagePaint);
            }
        }else{

                canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius , placeholderPaint);
        }
        if (mBorderWidth > 0 && hasBorder) {
            Color strokeColor = new Color(bootstrapBrand.defaultEdge(getContext()));
            borderPaint.setColor(strokeColor);
            canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, borderPaint);
        }
    }
}
